package com.geek.gkfood.framework.groovy;

import com.geek.gkfood.framework.dispatch.SpringCtxUtils;
import com.geek.gkfood.util.excp.GkFoodCodeEnum;
import com.geek.gkfood.util.excp.GkFoodException;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import redis.clients.jedis.Protocol;

/**
 * <h1>GroovyClassLoader 的包装类。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2023/9/29 15:04
 */
@Slf4j
@Component
public class GroovyClassLoaderWrapper {

    private static final GroovyClassLoader groovyClassLoader;

    static {
        CompilerConfiguration config = new CompilerConfiguration();
        config.setSourceEncoding(Protocol.CHARSET);
        groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
    }

    /**
     * <h2>解析 scriptBase64 并进行编译，然后写入到 Spring 的容器中，最后从 Spring 的容器中根据 beanName 读取实例对象。</h2>
     *
     * @param beanName:
     * @param scriptBase64
     * @return
     */
    public Object getBean(String beanName, String scriptBase64) {
        Object instance = null;
        try {
            instance = SpringCtxUtils.getBean(beanName);
        } catch (Exception e) {
            log.error("ingore exception, beanName=" + beanName, e);
        }
        if (instance != null) {
            return instance;
        }

        try {
            // 解密 base64，主要防止一些中文内容在传输的过程中被各种翻译成其他编码
            String script = new String(Base64.decodeBase64(scriptBase64), Protocol.CHARSET);

            // 编译 script 并且写入到 Spring 容器中
            initializingBean(beanName, script);

            // 根据 beanName 从 Spring 容器中读取实例对象
            instance = SpringCtxUtils.getBean(beanName);
            log.info("getBean succ, beanName={}, instance={}", beanName, instance);
        } catch (Exception e) {
            log.error("ingore exception, beanName=" + beanName, e);
        }
        return instance;
    }

    /**
     * <h2>看看 beanName 对应的实例对象，是否在 Spring 容器中。</h2>
     *
     * @param beanName
     * @return
     */
    public boolean contains(String beanName) {
        Object beanInstance = null;
        try {
            beanInstance = SpringCtxUtils.getBean(beanName);
        } catch (Exception e) {
            log.error("ingore exception, beanName=" + beanName, e);
        }

        return beanInstance != null;
    }

    /**
     * <h2>移除 Spring 的 bean。</h2>
     *
     * @param beanName
     */
    public void removeBean(String beanName) {
        try {
            GenericApplicationContext genericCtx =
                    (GenericApplicationContext) SpringCtxUtils.getContext();
            genericCtx.getDefaultListableBeanFactory().removeBeanDefinition(beanName);

            log.info("removeBean succ, beanName={}", beanName);
        } catch (NoSuchBeanDefinitionException e) {
            log.info("removeBean succ, beanName is not exists, beanName={}", beanName);
        } catch (Exception e) {
            log.info("removeBean failed, beanName={}", beanName);
        }
    }

    /**
     * 将 script 代码进行编译并实例化。
     *
     * @param beanName
     * @param script
     */
    private void initializingBean(String beanName, String script) {
        try {
            Class<?> clz = compile(script);

            applyBean2IocContainer(clz, beanName);
        } catch (Exception e) {
            throw new GkFoodException(GkFoodCodeEnum.INITIALIZING_BEAN_EXCEPTION,
                    ("initializingBean error, beanName=" + beanName + ", script=" + script), e);
        }
    }

    /**
     * 将 Java 代码编译成 Class 文件。
     *
     * @param script
     */
    private Class<?> compile(String script) {
        try {
            Class clz = groovyClassLoader.parseClass(script);

            log.info("compile succ, clz={}, clzAddr={}", clz, ObjectUtils.identityToString(clz));
            return clz;
        } catch (Exception e) {
            log.error("compile exception", e);
            throw new GkFoodException(GkFoodCodeEnum.COMPILE_SCRIPT_EXCEPTION, ("compile error, script=" + script));
        }
    }

    /**
     * <h2>将 Bean 应用到 IOC 容器中。</h2>
     *
     * @param clazz
     * @param beanName
     */
    private void applyBean2IocContainer(Class clazz, String beanName) {
        // 根据入参的 clazz 对象构建 bean 定义
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();

        // 然后将 bean 定义移交给 spring 进行后续对象的创建，并且注入到 spring 容器中
        ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) SpringCtxUtils.getContext();
        applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(beanDefinition, beanName);
        ((BeanDefinitionRegistry) applicationContext.getBeanFactory()).registerBeanDefinition(beanName, beanDefinition);
    }
}